//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace LightweightExecutor
{
    using System;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Xml.Serialization;
    using Microsoft.Research.eResearch.Execution;
    using Microsoft.Research.ScientificWorkflow.TridentComposer;
    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using SR = Microsoft.Research.DataLayer;
    using System.Text;

    public class Executor
    {
        /// <summary>
        /// The connection manager.
        /// </summary>
        private SR.ConnectionManager connMgr;

        /// <summary>
        /// The registry connection.
        /// </summary>
        private SR.Connection registryConnection;

        /// <summary>
        /// Initializes a new instance of the lightweight executor.
        /// </summary>
        public Executor()
        {
            this.InitializeRegistry();
            this.InitializeConnection();
        }

        /// <summary>
        /// Executes the given workflow.
        /// </summary>
        /// <param name="workflowId">
        /// Id of the workflow to execute.
        /// </param>
        /// <param name="workflowInputParameters">
        /// Workflow required input parameters.
        /// </param>
        public void Execute(Guid workflowId, string workflowInputParameters)
        {
            SR.Machine virtualNode = this.FetchVirtualNode();

            TridentWorkflowModel workflowModel = this.CreateWorkflowModel(workflowId);
            JobDetailModel newJob = new JobDetailModel();
            newJob.WorkflowId = workflowModel.Id;

            // Fetch workflow input Parameters.
            newJob.ParameterInfo = FetchWorkflowInputParameters(workflowInputParameters, workflowModel);

            // Create a job in registry.
            JobComposer workflowClient = new JobComposer(this.registryConnection);
            SR.Job newRegJob = workflowClient.ExecuteWFOnMachine(newJob, virtualNode, TridentAuthentication.LoggedInUser, false);

            newRegJob.Status = SR.JobStatus.StartPending;
            newRegJob.Save();

            JobExecutorProxy executor = new JobExecutorProxy((newRegJob as SR.IObject).ID);
            executor.Execute(true);
        }

        /// <summary>
        /// This function is used to craete the Workflow input parameters from workflow model 
        /// and then update the values based on the input parameters passed as argument.
        /// </summary>
        /// <param name="worfklowInputParameters">Workflow input parameters</param>
        /// <param name="workflowModel">Workflow model.</param>
        /// <returns>Updated workflow input parameters.</returns>
        private static WorkflowActivityParametersInfo FetchWorkflowInputParameters(
            string worfklowInputParameters,
            TridentWorkflowModel workflowModel)
        {
            // Create the list of parameters and their values for the workflow.
            WorkflowActivityParametersInfo inputParameter = workflowModel.CreateWorkflowParameterInfo();

            // Deserialize the parameters.
            XmlSerializer serializer = new XmlSerializer(typeof(WorkflowActivityParametersInfo));
            WorkflowActivityParametersInfo actualParameter = null;

            if (!string.IsNullOrEmpty(worfklowInputParameters))
            {
                try
                {
                    actualParameter = (WorkflowActivityParametersInfo)serializer.Deserialize(new StringReader(worfklowInputParameters));
                }
                catch (InvalidOperationException ex)
                {
                    TridentErrorHandler.HandleAndThrowBusinessLogicException(ex, TridentErrorConstants.ErrorNumber1500000001, "Input XML is invalid.");
                }
            }

            StringBuilder errorBuilder = null;

            bool errorExists = false;
            foreach (ActivityParameterInfo item in inputParameter.Parameters)
            {
                if (actualParameter != null)
                {
                    ActivityParameterInfo newItem = actualParameter.Parameters
                    .FirstOrDefault(param => param.ActivityUniqueName.Equals(item.ActivityUniqueName, StringComparison.OrdinalIgnoreCase) &&
                        param.Name.Equals(item.Name, StringComparison.OrdinalIgnoreCase));
                    if (newItem != null)
                    {
                        item.Value = newItem.Value;
                    }
                    else if (item.Value == null)
                    {
                        errorExists = true;
                    }
                }
                else if (item.Value == null)
                {
                    errorExists = true;
                }

                if (errorExists)
                {
                    if (null == errorBuilder)
                    {
                        errorBuilder = new StringBuilder(ManagementStudioResourceManager.GetString("ReqInputParamsMissing"));
                        errorBuilder.Append(Environment.NewLine);
                    }

                    errorBuilder.Append(item.ActivityUniqueName);
                    errorBuilder.Append(".");
                    errorBuilder.Append(item.Name);
                    errorBuilder.Append(Environment.NewLine);
                }
            }

            if (errorBuilder != null)
            {
                TridentErrorHandler.ThrowTridentCustomException(
                    TridentErrorConstants.ErrorNumber1500000001,
                    errorBuilder.ToString());
            }

            return inputParameter;
        }

        /// <summary>
        /// Initializes the registry.
        /// </summary>
        private void InitializeRegistry()
        {
            try
            {
                Assembly me = Assembly.GetExecutingAssembly();
                string myPath = Path.GetDirectoryName(me.Location);

                //Put the alternate location
                SR.ConnectionManager.AlternateProviderLocation = myPath;

                // Initialize Connection.
                SR.SR_Connection.Init();
                this.connMgr = SR.ConnectionManager.CreateCustom(
                    SR.ConnectionManager.CancelBehavior.ThrowException,
                    Path.Combine(myPath, "ConnStore-Application.configSources"));
                this.connMgr.UIStyle = SR.ConnectionManagerBase.UIStyleType.Lite;
            }
            catch (SR.ConnectionFailure connectionFailure)
            {
                TridentErrorHandler.HandleUIException(connectionFailure);
                Console.WriteLine(connectionFailure.InnerException.Message);
                Environment.Exit(0);
            }
            catch (InvalidOperationException exception)
            {
                TridentErrorHandler.HandleUIException(exception);
                Console.WriteLine(exception.Message);
                Environment.Exit(0);
            }
            catch (Exception exception)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(exception);
                Environment.Exit(0);
            }
        }

        /// <summary>
        /// Initialize the registry connection.
        /// </summary>
        private void InitializeConnection()
        {
            try
            {
                this.registryConnection = this.connMgr.PickConnection(SR.ConnectionManager.ConnectionUI.NeverShowUI);
                if (null != this.registryConnection)
                {
                    if (!Helper.IsValidConnection(this.registryConnection))
                    {
                        Console.WriteLine(TridentCommonResourceManager.GetString("InvalidRegistryConnection"));
                        Environment.Exit(0);
                    }

                    this.registryConnection.AutoSave = false;
                    TridentAuthentication authenticate = new TridentAuthentication(this.registryConnection);
                    authenticate.AuthenticateUser();
                }
                else
                {
                    TridentErrorHandler.HandleUIException(new TridentCustomException(TridentCommonResourceManager.GetString("RegistryNotSelected")));
                    Console.WriteLine(TridentCommonResourceManager.GetString("RegistryNotSelected"));
                    Environment.Exit(0);
                }
            }
            catch (SR.ConnectionFailure connectionFailure)
            {
                TridentErrorHandler.HandleUIException(connectionFailure);
                Console.WriteLine(connectionFailure.InnerException.Message);
                Environment.Exit(0);
            }
            catch (InvalidOperationException exception)
            {
                TridentErrorHandler.HandleUIException(exception);
                Console.WriteLine(exception.Message);
                Environment.Exit(0);
            }
            catch (TridentCustomException customExp)
            {
                TridentErrorHandler.HandleTridentCustomExceptionInUI(customExp, false);
                Console.WriteLine(customExp.Message);
                Environment.Exit(0);
            }
            catch (Exception exception)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(exception, false);
                Console.WriteLine(exception.Message);
                Environment.Exit(0);
            }
        }

        /// <summary>
        /// Retrieve the required workflow from registry.
        /// </summary>
        /// <param name="workflowId">The workflow ID.</param>
        /// <returns>The workflow object.</returns>
        private SR.Activity GetWorkflowById(Guid workflowId)
        {
            try
            {
                return SR.Activity.Load(workflowId, this.registryConnection);
            }
            catch (SR.RecordNotFoundException ex)
            {
                TridentErrorHandler.HandleAndThrowBusinessLogicException(
                    ex,
                    TridentErrorConstants.ErrorNumber1000000012,
                    ManagementStudioResourceManager.GetString("WorkflowRetrieveError"));
            }
            catch (Exception ex)
            {
                TridentErrorHandler.ProcessKnownExceptionsInBusinessLogic(ex);
                TridentErrorHandler.HandleAndThrowBusinessLogicException(
                    ex,
                    TridentErrorConstants.ErrorNumber1000000012,
                    ManagementStudioResourceManager.GetString("WorkflowRetrieveError"));
            }
            return null;
        }

        /// <summary>
        /// Creates the workflow model.
        /// </summary>
        /// <param name="workflowId">
        /// The id of the workflow to be executed.
        /// </param>
        /// <returns>
        /// Trident WorkflowModel.
        /// </returns>
        private TridentWorkflowModel CreateWorkflowModel(Guid workflowId)
        {
            SR.Activity workflow = this.GetWorkflowById(workflowId);
            if (null == workflow || workflow.IsDeleted)
            {
                TridentErrorHandler.ThrowTridentCustomException(
                    TridentErrorConstants.ErrorNumber1000000012,
                    ManagementStudioResourceManager.GetString("WorkflowRetrieveError"));
            }

            ModelFactory modelFactory = new ModelFactory(null, null);
            return new TridentWorkflowModel(workflow, modelFactory);
        }

        /// <summary>
        /// Fetches the virtual node. If no virtual node exists, creates one and returns the same.
        /// </summary>
        /// <returns>Virtual machine instance.</returns>
        private SR.Machine FetchVirtualNode()
        {
            SR.Machine virtualNode = SR.Machine.CommonSearches.GetVirtualMachineByName(Environment.MachineName, this.registryConnection);

            if (virtualNode == null)
            {
                virtualNode = SR.Machine.CreateVirtualMachine(Environment.MachineName, this.registryConnection);
            }

            return virtualNode;
        }
    }
}
